#pragma once
#include "meta.h"
#include <cmath>
#include <string>

namespace gc {
	/*Bounding Volume*/
	
	class AABB;
	class AABB_SOA;
	using BV = AABB;
	using BV_SOA = AABB_SOA;

	class AABB {
	public:
		__host__ __device__ AABB() { reset(); }
		__host__ __device__ AABB(const AABB& b) { ptMin = b.ptMin; ptMax = b.ptMax; }
		__host__ __device__ AABB(AABB& b) { ptMin = b.ptMin; ptMax = b.ptMax; }
		__host__ __device__ AABB(Float minx, Float miny, Float minz,
			Float maxx, Float maxy, Float maxz)
			:ptMin(makePoint3f(minx, miny, minz)),
			ptMax(makePoint3f(maxx, maxy, maxz)) {}

		__host__ __device__ void merge(const AABB &b);

		__host__ __device__ void merge(const Point3f &b);

		__host__ __device__ void merge(const Float x, const Float y, const Float z);

		__host__ __device__ bool overlap(const AABB &b) const;

		__host__ __device__ void reset();

		__host__ std::string toString();

		Point3f ptMin, ptMax;
	};

	// structure of array, mostly used in GPU
	class AABB_SOA {
	public:
		uint size;
	public:
		Float * minx, *miny, *minz;
		Float *maxx, *maxy, *maxz;

		__host__ __device__ AABB_SOA();
		// shallow copy
		__host__ __device__ AABB_SOA(const AABB_SOA &b) {
			size = b.size;
			minx = b.minx;
			miny = b.miny;
			minz = b.minz;
			maxx = b.maxx;
			maxy = b.maxy;
			maxz = b.maxz;
		}
		__host__ __device__ AABB_SOA(AABB_SOA &b) {
			size = b.size;
			minx = b.minx;
			miny = b.miny;
			minz = b.minz;
			maxx = b.maxx;
			maxy = b.maxy;
			maxz = b.maxz;
		}

		void malloc(uint size);

		void free();

		/* depricated: very poor performance.
		   use kernel function resetBVs instead.
		*/
		void reset();

		__device__ Point3f center(uint i) const {
			return makePoint3f(
				(minx[i] + maxx[i])*0.5f,
				(miny[i] + maxy[i])*0.5f,
			    (minz[i] + maxz[i])*0.5f
				);
		}

		__device__ Float volume(uint i) const {
			return (maxx[i] - minx[i]) * (maxy[i] - miny[i]) * (maxz[i] - minz[i]);
		}

		__device__ void setBV(uint i, const AABB_SOA& bvs, uint j) {
			minx[i] = bvs.minx[j], miny[i] = bvs.miny[j], minz[i] = bvs.minz[j];
			maxx[i] = bvs.maxx[j], maxy[i] = bvs.maxy[j], maxz[i] = bvs.maxz[j];
		}

		__device__ void setBV(uint i, const AABB& bv) {
			minx[i] = bv.ptMin.x, miny[i] = bv.ptMin.y, minz[i] = bv.ptMin.z;
			maxx[i] = bv.ptMax.x, maxy[i] = bv.ptMax.y, maxz[i] = bv.ptMax.z;
		}
		__device__ AABB getBV(uint i) const {
			return AABB(minx[i], miny[i], minz[i], maxx[i], maxy[i], maxz[i]);
		}

		__device__ void merge(uint idx, const Float x, const Float y, const Float z) {
			minx[idx] = fmin(minx[idx], x);
			miny[idx] = fmin(miny[idx], y);
			minz[idx] = fmin(minz[idx], z);
			maxx[idx] = fmax(maxx[idx], x);
			maxy[idx] = fmax(maxy[idx], y);
			maxz[idx] = fmax(maxz[idx], z);
		}

		__device__ void merge(uint idx, AABB &bv) {
			minx[idx] = fmin(minx[idx], bv.ptMin.x);
			miny[idx] = fmin(miny[idx], bv.ptMin.y);
			minz[idx] = fmin(minz[idx], bv.ptMin.z);
			maxx[idx] = fmax(maxx[idx], bv.ptMax.x);
			maxy[idx] = fmax(maxy[idx], bv.ptMax.y);
			maxz[idx] = fmax(maxz[idx], bv.ptMax.z);
		}

		__device__ bool overlap(uint i, const AABB& b) const {
			if (b.ptMin.x >maxx[i] || b.ptMax.x < minx[i]) return false;
			if (b.ptMin.y >maxy[i] || b.ptMax.y < miny[i]) return false;
			if (b.ptMin.z >maxz[i] || b.ptMax.z < minz[i]) return false;
			return true;
		}
		__device__ bool overlap(uint i, uint j) const {
			if (minx[j] >maxx[i] || maxx[j] < minx[i]) return false;
			if (miny[j] >maxy[i] || maxy[j] < miny[i]) return false;
			if (minz[j] >maxz[i] || maxz[j] < minz[i]) return false;
			return true;
		}
		__device__ bool overlap(uint i, const AABB_SOA &bvs, uint j) const {
			if (bvs.minx[j] >maxx[i] || bvs.maxx[j] < minx[i]) return false;
			if (bvs.miny[j] >maxy[i] || bvs.maxy[j] < miny[i]) return false;
			if (bvs.minz[j] >maxz[i] || bvs.maxz[j] < minz[i]) return false;
			return true;
		}
		__device__ bool contain(uint i, const Point3f &v) const {
			return v.x <= maxx[i] && v.x >= minx[i] &&
				v.y <= maxy[i] && v.y >= miny[i] &&
				v.z <= maxz[i] && v.z >= minz[i];
		}
	};
}